package org.red5.logging;

/*
 * RED5 Open Source Flash Server - http://code.google.com/p/red5/
 * 
 * Copyright (c) 2006-2010 by respective authors (see below). All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License as published by the Free Software 
 * Foundation; either version 2.1 of the License, or (at your option) any later 
 * version. 
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along 
 * with this library; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.red5.server.api.Red5;

import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.status.ErrorStatus;

/**
 * Logback appender for the Extended W3C format.
 * 
 * @see "http://www.w3.org/TR/WD-logfile.html"
 * @author Paul Gregoire (mondain@gmail.com)
 */
public class W3CAppender extends FileAppender<LoggingEvent> {
	
	/**
	#Software: Red5 0.7.1
	#Version: 1.0
	#Date: 1998-11-19 22:48:39
	#Fields: date time c-ip cs-username s-ip cs-method 
	*/

	//whether or not the header has been written
	private static boolean headerWritten;

	//events that are to be logged
	private static String events;

	//linked list to preserve order
	private static List<String> eventsList = new ArrayList<String>();	
	
	//fields that are to be logged
	private static String fields;
	
	//linked list to preserve order
	private static LinkedList<String> fieldList = new LinkedList<String>();
	
	public W3CAppender() {
		setPrudent(true);
	}

	public void setEvents(String events) {
		W3CAppender.events = events;
		//make a list out of the event names
		String[] arr = events.split(";");
		for (String s : arr) {
			eventsList.add(s); 
		}		
	}

	public String getEvents() {
		return events;
	}

	public void setFields(String fields) {	
		W3CAppender.fields = fields;
		//make a list out of the field names
		String[] arr = fields.split(";");
		for (String s : arr) {
			fieldList.add(s); 
		}
	}

	public String getFields() {	
		return fields;
	}

	@Override
	public synchronized void doAppend(LoggingEvent event) {
		//get the log message
		String message = event.getFormattedMessage();
		//look for w3c prefix
		if (!message.startsWith("W3C")) {
			return;
		}
		// http://logback.qos.ch/apidocs/ch/qos/logback/classic/spi/LoggingEvent.html
		StringBuilder sbuf = new StringBuilder(128);
		//see if header has been written
		if (!headerWritten) {
			//build the header
			StringBuilder sb = new StringBuilder("#Software: ");
			sb.append(Red5.VERSION);
			sb.append("\n#Version: 1.0");
			sb.append("\n#Date: ");
			sb.append(new Date());
			sb.append("\n#Fields: ");
			for (String field : fields.split(";")) {
				sb.append(field);
				sb.append(' ');
			}
			sb.append('\n');
			//String header = sb.toString();
			//System.out.print(header);
			sbuf.append(sb.toString());
			headerWritten = true;
			sb = null;
		}
		//break the message into pieces
		String[] arr = message.split(" ");
		//create a map
		Map<String, String> elements = new HashMap<String, String>(arr.length);
		int i = 0;
		for (String s : arr) {
			if ((i = s.indexOf(':')) != -1) {
				String key = s.substring(0, i);
				String value = s.substring(i + 1);
				//System.out.println("Key: " + key + " Value: " + value);
				elements.put(key, value);
			}
		}
		//Events					Categories
        //connect-pending			session
        //connect					session                     
        //disconnect                session                     
        //publish                   stream                         
        //unpublish                 stream                  
        //play                      stream                       
        //pause                     stream                     
        //unpause                   stream                      
        //seek                      stream                              
        //stop                      stream                       
        //record                    stream                              
        //recordstop                stream                              
        //server-start              server                              
        //server-stop               server                              
        //vhost-start               vhost                               
        //vhost-stop                vhost                               
        //app-start                 application                         
        //app-stop                  application    
		//filter based on event type - asterik allows all events
		if (!events.equals("*")) {
    		if (!eventsList.contains(elements.get("x-event"))) {
    			//System.err.println("Filtered out - event: "+elements.get("x-event")+" event list: "+eventsList);
    			elements.clear();
    			elements = null;
    			sbuf = null;
    			return;
    		}
		}
		//x-category		event category		
		//x-event			type of event
		//date				date at which the event occurred
		//time				time at which the event occurred
		//tz               	time zone information                           
		//x-ctx            	event dependant context information             
		//s-ip		        ip address[es] of the server                    
		//x-pid            	server process id                               
		//x-cpu-load       	cpu load                                        
		//x-mem-load       	memory load (as reported in getServerStats)     
		//x-adaptor        	adaptor name                                    
		//x-vhost          	vhost name                                      
		//x-app	          	application name                                
		//x-appinst        	application instance name                       
		//x-duration	    duration of an event/session                    
		//x-status		    status code					                            
		//c-ip             	client ip address                               
		//c-proto          	connection protocol - rtmp or rtmpt             
		//s-uri            	uri of the fms application                      
		//cs-uri-stem      	stem of s-uri                                   
		//cs-uri-query     	query portion of s-uri                          
		//c-referrer       	uri of the referrer                             
		//c-user-agent     	user agent                                      
		//c-client-id      	client id                                       
		//cs-bytes         	bytes transferred from client to server         
		//sc-bytes         	bytes transferred from server to client         
		//c-connect-type   	type of connection received by the server       
		//x-sname          	stream name                                     
		//x-sname-query    	query portion of stream uri                     
		//x-suri-query		same as x-sname-query						               	
		//x-suri-stem		cs-uri-stem + x-sname + x-file-ext			       	
		//x-suri			x-suri-stem + x-suri-query						          
		//x-file-name      	full file path of recorded stream               
		//x-file-ext       	stream type (flv or mp3)                        
		//x-file-size      	stream size in bytes                            
		//x-file-length    	stream length in seconds                        
		//x-spos           	stream position                                 
		//cs-stream-bytes  	stream bytes transferred from client to server  
		//sc-stream-bytes  	stream bytes transferred from server to client  
		//x-service-name   	name of the service providing the connection    
		//x-sc-qos-bytes	bytes transferred from server to client for quality of service	
		//x-comment	      	comments		
		//we may need date and/or time
		Calendar cal = GregorianCalendar.getInstance();
		cal.clear();
		cal.setTimeInMillis(event.getTimeStamp());
		//loop through the field names and grab the values from the map
		//fields without a value get a tab character as a place holder if
		//their value is not available to the server
		for (String field : fieldList) {
			String value = elements.get(field);
			if (value == null) {
				if ("date".equals(field)) {
					sbuf.append(cal.get(Calendar.MONTH) + 1);
					sbuf.append('/');
					sbuf.append(cal.get(Calendar.DAY_OF_MONTH));
					sbuf.append('/');
					sbuf.append(cal.get(Calendar.YEAR));
				} else if ("time".equals(field)) {
					sbuf.append(cal.get(Calendar.HOUR_OF_DAY));
					sbuf.append(':');
					int min = cal.get(Calendar.MINUTE);
					if (min < 10) {
						sbuf.append('0');
						sbuf.append(min);					
					} else {
						sbuf.append(min);					
					}
				} else if ("s-ip".equals(field)) {		
					//where should we grab the server ip from?
					sbuf.append("127.0.0.1");
				} else if ("x-pid".equals(field)) {		
					//should we pass thread name?
					sbuf.append(event.getThreadName());
				} else {			
					sbuf.append('\t');
				}
			} else {
				sbuf.append(value);
			}	    
			//space padded
			sbuf.append(' ');
		}
		sbuf.append('\n');
		//System.out.println(sbuf.toString());		
		try {
			//switch out the message
			event.setMessage(sbuf.toString());
			//write it
			writeOut(event);
		} catch (IOException ioe) {
			addStatus(new ErrorStatus("IO failure in appender", this, ioe));
		}
	}

}
